﻿// ===============================================================================
// XCI.NET 西安交通信息WinForm开发框架 2018 版权所有
// ===============================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using XCI.Helper;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System.Threading.Tasks;
using NLog;
using XCI.Extensions;

namespace XCI.Core
{
    /// <summary>
    /// WebApi接口请求组件
    /// </summary>
    public class WebApiClient
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        public static readonly MediaTypeHeaderValue JsonMediaTypeHeader = new MediaTypeHeaderValue("application/json");
        public static readonly MediaTypeHeaderValue FormUrlEncodedMediaTypeHeader = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        //public static readonly Type StringType = typeof(string);
        public const int UploadReadBufferSize = 81920;
        public const int DownloadReadBufferSize = 81920;

        #region 构造函数

        /// <summary>
        /// 静态构造函数
        /// </summary>
        static WebApiClient()
        {
            ServicePointManager.DefaultConnectionLimit = 5000;
            //启用保活机制（保持活动超时设置为 2 小时，并将保持活动间隔设置为 1 秒。）
            ServicePointManager.SetTcpKeepAlive(true, 7200000, 1000);
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.UseNagleAlgorithm = false;
        }

        public WebApiClient(WebApiConfig config)
        {
            this.Config = config ?? throw new ArgumentNullException(nameof(config));
            InitComponent();
        }

        #endregion

        #region 属性

        /// <summary>
        /// 接口服务器配置
        /// </summary>
        public WebApiConfig Config { get; }

        /// <summary>
        /// 微软内置Http请求组件
        /// </summary>
        public HttpClient Client { get; private set; }

        /// <summary>
        /// Json序列化组件设置
        /// </summary>
        public JsonSerializerSettings SerializerSettings { get; private set; }

        /// <summary>
        /// Json序列化组件
        /// </summary>
        public JsonSerializer Serializer { get; private set; }

        /// <summary>
        /// 服务器基地址
        /// </summary>
        protected string BaseUrl { get; private set; }

        #endregion

        #region Create

        /// <summary>
        /// 创建Http请求组件
        /// </summary>
        public virtual HttpClient CreateHttpClient()
        {
            this.BaseUrl = InitBaseUrl();

            var handler = new HttpClientHandler
            {
                UseCookies = false,
                AllowAutoRedirect = false,
                UseProxy = Config.UseProxy,
                AutomaticDecompression = DecompressionMethods.GZip
            };
            if (!Config.UseProxy) handler.Proxy = null;

            var client = new HttpClient(handler);
            client.DefaultRequestHeaders.ExpectContinue = false;
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
            client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Connection.Add("keep-alive");
            client.Timeout = TimeSpan.FromSeconds(Config.Timeout);
            OnInitDefaultRequestHeaders(client.DefaultRequestHeaders);
            return client;
        }

        /// <summary>
        /// 创建Json序列化组件设置
        /// </summary>
        public virtual JsonSerializerSettings CreateJsonSerializerSettings()
        {
            var settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Error,
                NullValueHandling = NullValueHandling.Ignore,
                DateFormatString = "yyyy-MM-dd HH:mm:ss",
                ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
                Formatting = Formatting.None
            };
            settings.Converters.Add(new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() });
            return settings;
        }

        /// <summary>
        /// 创建Json序列化组件
        /// </summary>
        public virtual JsonSerializer CreateJsonSerializer()
        {
            if (SerializerSettings == null)
            {
                this.SerializerSettings = CreateJsonSerializerSettings();
            }
            return JsonSerializer.Create(SerializerSettings);
        }

        /// <summary>
        /// 创建Http内容对象
        /// </summary>
        /// <param name="option">请求选项</param>
        /// <param name="log">请求日志配置</param>
        public virtual HttpContent CreateHttpContent(WebApiPostOption option, WebApiLogConfig log)
        {
            var body = string.Empty;
            var contentType = option.ContentType;
            HttpContent content = null;
            if (contentType == WebApiContentType.Json)
            {
                body = SerializeObject(option.Data);
                if (option.Encrypt) body = EncryptHelper.AESEncrypt(body);
                content = new StringContent(body, Encoding.UTF8);
                content.Headers.ContentType = JsonMediaTypeHeader;
            }
            else if (contentType == WebApiContentType.FormUrlEncoded)
            {
                var nameValueCollection = ConvertDicToCollection(ConvertDataToDictionary(option.Data));
                content = new FormUrlEncodedContent(nameValueCollection);
                content.Headers.ContentType = FormUrlEncodedMediaTypeHeader;
                if (log.EnableRequestLog) body = WebHelper.GetFormUrlEncodedContent(nameValueCollection);
            }
            else if (contentType == WebApiContentType.File)
            {
                var formDataContent = new MultipartFormDataContent();
                content = formDataContent;
                string filePath = option.UploadFilePath;
                if (File.Exists(filePath))
                {
                    formDataContent.Add(new StreamContent(File.OpenRead(filePath), UploadReadBufferSize), "file", Path.GetFileName(filePath));
                }
                var formData = ConvertDataToDictionary(option.Data);
                if (formData != null && formData.Count > 0)
                {
                    foreach (var item in formData)
                    {
                        formDataContent.Add(new StringContent(item.Value.ToStringOrEmpty()), item.Key);
                    }
                }
                if (log.EnableRequestLog) body = $"uploadFile: \"{filePath}\" \n\t      formData: \"{WebHelper.GetFormUrlEncodedContent(formData)}\"";
            }
            if (log.EnableRequestLog) WriteRequestBodyLog(body);
            return content;
        }

        #endregion

        #region Log

        public virtual void WriteRequestLog(HttpRequestMessage request)
        {
            StringBuilder sb = new StringBuilder();
            if (request != null)
            {
                sb.Append($"Request:\n\t{request.Method} {request.RequestUri} {request.RequestUri.Scheme.ToUpper()}/{request.Version}");
                if (Client.DefaultRequestHeaders.Any()) sb.Append(GetHttpHeaderLog("DefaultHeaders", Client.DefaultRequestHeaders));
                sb.Append(GetHttpHeaderLog("Headers", request.Headers));
            }

            Logger.Info(sb.ToString);
        }

        public virtual void WriteRequestBodyLog(string body)
        {
            Logger.Info($"\n\tBody: {body}");
        }

        private string GetHttpHeaderLog(string name, HttpRequestHeaders headers)
        {
            if (headers == null) return string.Empty;
            var sb = new StringBuilder();
            foreach (var item in headers)
            {
                if (item.Key.Equals("Accept-Encoding") || item.Key.Equals("Accept") || item.Key.Equals("Connection")) continue;
                string val = item.Value.Count() > 1
                    ? SerializeObject(item.Value)
                    : SerializeObject(item.Value.FirstOrDefault());
                sb.Append($"\t\t{item.Key}: {val}\n");
            }

            if (sb.Length > 0)
            {
                return "\n\t" + name + ":\n" + sb.ToString().TrimEnd('\n');
            }

            return string.Empty;
        }

        public virtual void WriteResponseStatusLog(HttpResponseMessage response, TimeSpan elapsed)
        {
            if (response == null) return;
            Logger.Info($"Response: Status = {(int)response.StatusCode}, Time = {elapsed.TotalMilliseconds:###0.##}ms");
        }

        public virtual void WriteResponseContentLog(HttpResponseMessage response, string content)
        {
            if (response == null || content == null) return;
            Logger.Info($"Response: {content}");
        }

        #endregion

        #region Callback

        /// <summary>
        /// 初始化默认请求头
        /// </summary>
        /// <param name="defaultRequestHeaders">默认请求头</param>
        public virtual void OnInitDefaultRequestHeaders(HttpRequestHeaders defaultRequestHeaders)
        {
        }

        /// <summary>
        /// 发起Http请求之前触发
        /// </summary>
        /// <param name="request">Http请求对象</param>
        public virtual void OnBeforeRequest(HttpRequestMessage request)
        {

        }

        /// <summary>
        /// Http响应之后触发
        /// </summary>
        /// <param name="response">Http响应对象</param>
        public virtual void OnAfterResponse(HttpResponseMessage response)
        {
        }

        #endregion

        #region Util

        /// <summary>
        /// 创建完整请求Url
        /// </summary>
        /// <param name="url">接口Url</param>
        public virtual string BuildFullUrl(string url)
        {
            if (string.IsNullOrEmpty(url)) throw new ArgumentNullException(nameof(url));
            string startVar = string.Empty;
            if (!url.StartsWith("/"))
            {
                startVar = "/";
            }
            return $"{BaseUrl}{startVar}{url}";
        }

        /// <summary>
        /// 转换响应结果为字符串
        /// </summary>
        /// <param name="response">Http响应对象</param>
        public virtual async Task<string> ReadResponseToString(HttpResponseMessage response)
        {
            if (response == null) return string.Empty;
            response.RequestMessage?.Content?.Dispose();
            var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
            if (stream == null || stream.CanRead == false) return string.Empty;

            using (stream)
            {
                using (var streamReader = new StreamReader(stream))
                {
                    var str = await streamReader.ReadToEndAsync();
                    if (RequiredDecryptResponse(response) && !str.StartsWith("{"))
                    {
                        return EncryptHelper.AESDecrypt(str);
                    }
                    return str;
                }
            }
        }

        /// <summary>
        /// 转换响应结果为对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="response">Http响应对象</param>
        public virtual async Task<T> ReadResponseToObject<T>(HttpResponseMessage response)
        {
            if (response == null) return default(T);
            response.RequestMessage?.Content?.Dispose();
            var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
            if (stream == null || stream.CanRead == false) return default(T);

            using (stream)
            {
                using (var streamReader = new StreamReader(stream))
                {
                    if (!RequiredDecryptResponse(response)) return DeserializeStreamReader<T>(streamReader);
                    var str = await streamReader.ReadToEndAsync();
                    if (str.StartsWith("{")) return DeserializeStreamReader<T>(new StringReader(str));

                    var encryptStr = EncryptHelper.AESDecrypt(str);
                    return DeserializeStreamReader<T>(new StringReader(encryptStr));
                }
            }
        }


        private T DeserializeStreamReader<T>(TextReader streamReader)
        {
            using (var jtr = new JsonTextReader(streamReader))
            {
                return Serializer.Deserialize<T>(jtr);
            }
        }

        private bool RequiredDecryptResponse(HttpResponseMessage response)
        {
            if (response.Headers.TryGetValues("encrypt", out IEnumerable<string> vals))
            {
                var encrypt = vals.ToList().FirstOrDefault();
                if (!string.IsNullOrEmpty(encrypt) && encrypt.Equals("1"))
                {
                    return true;
                }
            }
            return false;
        }

        public virtual string SerializeObject(object data)
        {
            if (data == null) return string.Empty;
            using (StringWriter stringWriter = new StringWriter())
            {
                Serializer.Serialize(new JsonTextWriter(stringWriter), data);
                return stringWriter.GetStringBuilder().ToString();
            }
        }

        #endregion

        #region Get

        /// <summary>
        /// 发送Get请求,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public Task GetAsync(string url, object data = null)
        {
            return GetAsync<object>(new WebApiGetOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Get请求,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="option">请求选项</param>
        public Task GetAsync(WebApiGetOption option)
        {
            return GetAsync<object>(option);
        }

        /// <summary>
        /// 发送Get请求,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public Task<T> GetAsync<T>(string url, object data = null)
        {
            return GetAsync<T>(new WebApiGetOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Get请求,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public async Task<T> GetAsync<T>(WebApiGetOption option)
        {
            var result = await GetAsBoolResultAsync<T>(option);
            if (result.Fail) throw new WebApiClientBoolResultException(result);
            return result.Data;
        }

        /// <summary>
        /// 发送Get请求,返回BoolResult
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<object>> GetAsBoolResultAsync(string url, object data)
        {
            return GetAsBoolResultAsync<object>(new WebApiGetOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Get请求,返回BoolResult
        /// </summary>
        /// <param name="option">请求选项</param>
        public virtual Task<BoolResult<object>> GetAsBoolResultAsync(WebApiGetOption option)
        {
            return GetAsBoolResultAsync<object>(option);
        }

        /// <summary>
        /// 发送Get请求,返回响应字符串
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<string> GetAsStringAsync(string url, object data = null)
        {
            return GetAsStringAsync(new WebApiGetOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Get请求,返回响应字符串
        /// </summary>
        /// <param name="option">请求选项</param>
        public virtual async Task<string> GetAsStringAsync(WebApiGetOption option)
        {
            var log = option.Log ?? Config.Log;
            Stopwatch watch = null;
            if (log.EnableResponseStatusLog) watch = DebugHelper.StartWatch();
            var requestUrl = WebHelper.BuildGetUrl(BaseUrl, option.Url, ConvertDataToDictionary(option.Data));
            using (var request = new HttpRequestMessage(HttpMethod.Get, requestUrl))
            {
                if (log.EnableRequestLog) WriteRequestLog(request);
                OnBeforeRequest(request);
                AddRequestHeaders(request, option.Headers);
                using (var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, GetCancellationToken(option.CancellationSource)).ConfigureAwait(false))
                {
                    response.EnsureSuccessStatusCode();
                    OnAfterResponse(response);
                    var result = await ReadResponseToString(response);
                    if (log.EnableResponseStatusLog) WriteResponseStatusLog(response, DebugHelper.EndWatch(watch));
                    if (log.EnableResponseContentLog) WriteResponseContentLog(response, result);
                    return result;
                }
            }
        }

        /// <summary>
        /// 发送Get请求,返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<T>> GetAsBoolResultAsync<T>(string url, object data)
        {
            return GetAsBoolResultAsync<T>(new WebApiGetOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Get请求,返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public virtual async Task<BoolResult<T>> GetAsBoolResultAsync<T>(WebApiGetOption option)
        {
            var log = option.Log ?? Config.Log;
            Stopwatch watch = null;
            if (log.EnableResponseStatusLog) watch = DebugHelper.StartWatch();
            var requestUrl = WebHelper.BuildGetUrl(BaseUrl, option.Url, ConvertDataToDictionary(option.Data));
            using (var request = new HttpRequestMessage(HttpMethod.Get, requestUrl))
            {
                if (log.EnableRequestLog) WriteRequestLog(request);
                OnBeforeRequest(request);
                AddRequestHeaders(request, option.Headers);
                using (var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, GetCancellationToken(option.CancellationSource)).ConfigureAwait(false))
                {
                    response.EnsureSuccessStatusCode();
                    OnAfterResponse(response);
                    var result = await ReadResponseToObject<BoolResult<T>>(response);
                    if (log.EnableResponseStatusLog) WriteResponseStatusLog(response, DebugHelper.EndWatch(watch));
                    if (log.EnableResponseContentLog) WriteResponseContentLog(response, SerializeObject(result));
                    return result;
                }
            }
        }

        #endregion

        #region Post

        /// <summary>
        /// 发送Post请求,采用Json序列化,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public Task PostAsync(string url, object data = null)
        {
            return PostAsync<object>(new WebApiPostOption { Url = url, Data = data, ContentType = WebApiContentType.Json });
        }

        /// <summary>
        /// 发送Post请求,采用Json序列化,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="option">请求选项</param>
        public Task PostAsync(WebApiPostOption option)
        {
            return PostAsync<object>(option);
        }

        /// <summary>
        /// 发送Post请求,采用Json序列化,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public Task<T> PostAsync<T>(string url, object data = null)
        {
            return PostAsync<T>(new WebApiPostOption { Url = url, Data = data, ContentType = WebApiContentType.Json });
        }

        /// <summary>
        /// 发送Post请求,采用Json序列化,反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public async Task<T> PostAsync<T>(WebApiPostOption option)
        {
            var result = await PostAsBoolResultAsync<T>(option);
            if (result.Fail) throw new WebApiClientBoolResultException(result);
            return result.Data;
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<object>> PostAsBoolResultAsync(string url, object data = null)
        {
            return PostAsBoolResultAsync<object>(new WebApiPostOption { Url = url, Data = data, ContentType = WebApiContentType.Json });
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult
        /// </summary>
        /// <param name="option">请求选项</param>
        public virtual Task<BoolResult<object>> PostAsBoolResultAsync(WebApiPostOption option)
        {
            return PostAsBoolResultAsync<object>(option);
        }

        /// <summary>
        /// 发送Post请求,返回响应字符串
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<string> PostAsStringAsync(string url, object data = null)
        {
            return PostAsStringAsync(new WebApiPostOption { Url = url, Data = data });
        }

        /// <summary>
        /// 发送Post请求,返回响应字符串
        /// </summary>
        /// <param name="option">请求选项</param>
        public virtual async Task<string> PostAsStringAsync(WebApiPostOption option)
        {
            var log = option.Log ?? Config.Log;
            Stopwatch watch = null;
            if (log.EnableResponseStatusLog) watch = DebugHelper.StartWatch();
            var requestUrl = WebHelper.BuildPostUrl(BaseUrl, option.Url);
            using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl))
            {
                if (log.EnableRequestLog) WriteRequestLog(request);
                using (var content = CreateHttpContent(option, log))
                {
                    request.Content = content;
                    OnBeforeRequest(request);
                    AddRequestHeaders(request, option.Headers);
                    using (var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, GetCancellationToken(option.CancellationSource)).ConfigureAwait(false))
                    {
                        response.EnsureSuccessStatusCode();
                        OnAfterResponse(response);
                        var result = await ReadResponseToString(response);
                        if (log.EnableResponseStatusLog) WriteResponseStatusLog(response, DebugHelper.EndWatch(watch));
                        if (log.EnableResponseContentLog) WriteResponseContentLog(response, result);
                        return result;
                    }
                }
            }
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<T>> PostAsBoolResultAsync<T>(string url, object data = null)
        {
            return PostAsBoolResultAsync<T>(new WebApiPostOption { Url = url, Data = data, ContentType = WebApiContentType.Json });
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public virtual async Task<BoolResult<T>> PostAsBoolResultAsync<T>(WebApiPostOption option)
        {
            var log = option.Log ?? Config.Log;
            Stopwatch watch = null;
            if (log.EnableResponseStatusLog) watch = DebugHelper.StartWatch();
            var requestUrl = WebHelper.BuildPostUrl(BaseUrl, option.Url);
            using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl))
            {
                if (log.EnableRequestLog) WriteRequestLog(request);
                OnBeforeRequest(request);
                AddRequestHeaders(request, option.Headers);
                if (option.Encrypt) request.Headers.Add("encrypt","1");
                
                using (var content = CreateHttpContent(option, log))
                {
                    request.Content = content;
                    using (var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, GetCancellationToken(option.CancellationSource)).ConfigureAwait(false))
                    {
                        response.EnsureSuccessStatusCode();
                        OnAfterResponse(response);
                        var result = await ReadResponseToObject<BoolResult<T>>(response);
                        if (log.EnableResponseStatusLog) WriteResponseStatusLog(response, DebugHelper.EndWatch(watch));
                        if (log.EnableResponseContentLog) WriteResponseContentLog(response, SerializeObject(result));
                        return result;
                    }
                }
            }
        }

        #endregion

        #region Upload

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="uploadFilePath">上传的文件路径</param>
        /// <param name="data">请求数据</param>
        public Task UploadAsync(string url, string uploadFilePath, object data = null)
        {
            var option = new WebApiPostOption
            { Url = url, Data = data, ContentType = WebApiContentType.File, UploadFilePath = uploadFilePath };
            return UploadAsync<object>(option);
        }

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="option">请求选项</param>
        public Task UploadAsync(WebApiPostOption option)
        {
            AddUploadFileToken(option);
            return UploadAsync<object>(option);
        }

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="uploadFilePath">上传的文件路径</param>
        /// <param name="data">请求数据</param>
        public Task<T> UploadAsync<T>(string url, string uploadFilePath, object data = null)
        {
            var option = new WebApiPostOption
            { Url = url, Data = data, ContentType = WebApiContentType.File, UploadFilePath = uploadFilePath };
            return UploadAsync<T>(option);
        }

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),反序列化结果字符串,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException,返回BoolResult.Data
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public async Task<T> UploadAsync<T>(WebApiPostOption option)
        {
            AddUploadFileToken(option);
            var result = await PostAsBoolResultAsync<T>(option);
            if (result.Fail) throw new WebApiClientBoolResultException(result);
            return result.Data;
        }

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),返回BoolResult
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="uploadFilePath">上传的文件路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<object>> UploadAsBoolResultAsync(string url, string uploadFilePath, object data = null)
        {
            var option = new WebApiPostOption
            { Url = url, Data = data, ContentType = WebApiContentType.File, UploadFilePath = uploadFilePath };
            AddUploadFileToken(option);
            return PostAsBoolResultAsync<object>(option);
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult
        /// </summary>
        /// <param name="option">请求选项</param>
        public virtual Task<BoolResult<object>> UploadAsBoolResultAsync(WebApiPostOption option)
        {
            AddUploadFileToken(option);
            return PostAsBoolResultAsync<object>(option);
        }

        /// <summary>
        /// 文件上传,采用Post+(multipart/form-data),返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="url">接口路径</param>
        /// <param name="uploadFilePath">上传的文件路径</param>
        /// <param name="data">请求数据</param>
        public virtual Task<BoolResult<T>> UploadAsBoolResultAsync<T>(string url, string uploadFilePath, object data = null)
        {
            var option = new WebApiPostOption
            { Url = url, Data = data, ContentType = WebApiContentType.File, UploadFilePath = uploadFilePath };
            AddUploadFileToken(option);
            return PostAsBoolResultAsync<T>(option);
        }

        /// <summary>
        /// 发送Post请求,返回BoolResult&lt;T&gt;
        /// </summary>
        /// <typeparam name="T">服务端结果对象</typeparam>
        /// <param name="option">请求选项</param>
        public virtual Task<BoolResult<T>> UploadAsBoolResultAsync<T>(WebApiPostOption option)
        {
            AddUploadFileToken(option);
            return PostAsBoolResultAsync<T>(option);
        }

        private const string UPLOAD_NAME_TOKEN_HEADER_KEY = "uploadNameToken";
        private void AddUploadFileToken(WebApiPostOption option)
        {
            if (option.Headers == null) option.Headers = new Dictionary<string, string>();
            option.Headers.Add(UPLOAD_NAME_TOKEN_HEADER_KEY, EncryptHelper.AESEncrypt(Path.GetFileName(option.UploadFilePath)));
        }

        #endregion

        #region Download

        /// <summary>
        /// 文件下载,采用Post+Json,如果BoolResult为失败状态,则抛出WebApiClientBoolResultException
        /// </summary>
        /// <param name="url">接口路径</param>
        /// <param name="saveFilePath">下载保存的文件路径</param>
        /// <param name="data">请求数据</param>
        public virtual async Task DownloadAsync(string url, string saveFilePath, object data)
        {
            var result = await DownloadAsync(new WebApiDownloadOption { Url = url, SaveFilePath = saveFilePath, Data = data, Method = HttpMethod.Post, ContentType = WebApiContentType.Json });
            if (result.Fail) throw new WebApiClientDownloadException(result);
        }

        /// <summary>
        /// 文件下载
        /// </summary>
        /// <param name="option">下载请求选项</param>
        public virtual async Task<BoolResult> DownloadAsync(WebApiDownloadOption option)
        {
            var log = option.Log ?? Config.Log;
            Stopwatch watch = null;
            if (log.EnableResponseStatusLog) watch = DebugHelper.StartWatch();
            var requestUrl = option.Method == HttpMethod.Get ?
                WebHelper.BuildGetUrl(BaseUrl, option.Url, ConvertDataToDictionary(option.Data)) :
                WebHelper.BuildPostUrl(BaseUrl, option.Url);
            using (var request = new HttpRequestMessage(option.Method, requestUrl))
            {
                if (log.EnableRequestLog) WriteRequestLog(request);
                OnBeforeRequest(request);
                AddRequestHeaders(request, option.Headers);
                if (option.Method == HttpMethod.Post) request.Content = CreateHttpContent(option, log);
                using (var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, GetCancellationToken(option.CancellationSource)).ConfigureAwait(false))
                {
                    response.EnsureSuccessStatusCode();
                    OnAfterResponse(response);
                    BoolResult result;
                    var responseContentType = response.Content.Headers.ContentType;
                    if (responseContentType == null)
                    {
                        result = new BoolResult(false, "下载路径无效,未找到指定的文件");
                    }
                    else if (responseContentType.MediaType.StartsWith("application/md5-identical", StringComparison.OrdinalIgnoreCase))
                    {
                        result = new BoolResult(true, "md5一致,不需要下载", null);
                    }
                    else
                    {
                        // if (responseContentType.MediaType.StartsWith("application/octet-stream", StringComparison.OrdinalIgnoreCase)   || responseContentType.MediaType.IndexOf("image", StringComparison.OrdinalIgnoreCase) > -1)
                        result = await SaveDownloadFile(response, option);
                    }

                    if (log.EnableResponseStatusLog)
                    {
                        WriteResponseStatusLog(response, DebugHelper.EndWatch(watch));
                        WriteResponseContentLog(response, SerializeObject(result));
                    }

                    return result;
                }
            }
        }

        private async Task<BoolResult> SaveDownloadFile(HttpResponseMessage response, WebApiDownloadOption option)
        {
            using (Stream stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
            {
                if (stream == null || stream.CanRead == false) return new BoolResult(false, "下载内容为空");

                var contentLength = response.Content.Headers.ContentLength;
                IOHelper.CreateDirectoryByPath(option.SaveFilePath);
                using (var fileStream = File.Open(option.SaveFilePath, FileMode.Create))
                {
                    int count;
                    byte[] buffer = new byte[DownloadReadBufferSize];
                    var args = new WebApiDownloadProgressArgs();
                    if (option.ProgressCallback != null)
                    {
                        if (contentLength.HasValue) args.TotalBytes = contentLength.Value;
                        option.ProgressCallback.Invoke(args);
                    }

                    while ((count = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
                    {
                        await fileStream.WriteAsync(buffer, 0, count);
                        if (option.ProgressCallback != null)
                        {
                            args.TransferredBytes += count;
                            var current = Convert.ToInt32(args.TransferredBytes / 1024m);
                            var total = Convert.ToInt32(args.TotalBytes / 1024m);
                            args.ProgressPercentage = 0;
                            if (total > 0)
                            {
                                args.ProgressPercentage = Convert.ToInt32(current * 100m / total);
                            }
                            option.ProgressCallback.Invoke(args);
                        }
                    }
                }
            }
            return BoolResult.True;
        }

        #endregion

        #region 私有函数

        private void InitComponent()
        {
            this.Client = CreateHttpClient();
            this.Serializer = CreateJsonSerializer();
        }

        private string InitBaseUrl()
        {
            var baseUrl = Config.BaseUrl;
            if (string.IsNullOrEmpty(baseUrl)) return string.Empty;
            if (baseUrl.EndsWith("/"))
            {
                baseUrl = baseUrl.Substring(0, baseUrl.Length - 1);
            }

            return baseUrl;
        }

        private void AddRequestHeaders(HttpRequestMessage request, IDictionary<string, string> headers)
        {
            if (headers != null && headers.Count > 0)
            {
                foreach (KeyValuePair<string, string> pair in headers)
                {
                    request.Headers.Add(pair.Key, pair.Value);
                }
            }
        }

        private CancellationToken GetCancellationToken(CancellationTokenSource cancellationSource)
        {
            if (cancellationSource == null) return CancellationToken.None;
            return cancellationSource.Token;
        }

        /// <summary>
        /// 将请求对象转为字典
        /// </summary>
        /// <param name="data">数据对象</param>
        protected virtual IDictionary<string, string> ConvertDataToDictionary(object data)
        {
            if (data == null) return new Dictionary<string, string>();
            if (data is IDictionary<string, string> dictionary)
            {
                return dictionary;
            }
            var dic = new Dictionary<string, string>();
            foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(data))
            {
                dic.Add(propertyDescriptor.Name, propertyDescriptor.GetValue(data).ToStringOrEmpty());
            }
            return dic;
        }

        /// <summary>
        /// 将字典转为键值对集合
        /// </summary>
        /// <param name="dic">字典对象</param>
        protected virtual IDictionary<string, string> ConvertDicToCollection(IDictionary<string, string> dic)
        {
            var data = new Dictionary<string, string>();
            if (dic == null) return data;
            foreach (KeyValuePair<string, string> item in dic)
            {
                data[item.Key] = item.Value.ToStringOrEmpty();
            }
            return data;
        }

        #endregion
    }

    /// <summary>
    /// 接口服务器配置
    /// </summary>
    public class WebApiConfig
    {
        /// <summary>
        /// 服务器基地址
        /// </summary>
        public string BaseUrl { get; set; }

        /// <summary>
        /// 上传文件http访问根路径
        /// </summary>
        public string UploadRootUrl { get; set; }

        /// <summary>
        /// 服务器请求超时,单位秒
        /// </summary>
        public int Timeout { get; set; } = 10;

        /// <summary>
        /// 处理程序是否对请求使用代理
        /// </summary>
        public bool UseProxy { get; set; } = true;

        /// <summary>
        /// 请求日志配置
        /// </summary>
        public WebApiLogConfig Log { get; set; } = new WebApiLogConfig();
    }

    /// <summary>
    /// 请求组件日志配置
    /// </summary>
    public class WebApiLogConfig
    {
        /// <summary>
        /// 打开/关闭请求日志（默认为 true）
        /// </summary>
        public bool EnableRequestLog { get; set; } = true;

        /// <summary>
        /// 打开/关闭响应状态日志（默认为 true）
        /// </summary>
        public bool EnableResponseStatusLog { get; set; } = true;

        /// <summary>
        /// 打开/关闭响应内容日志（默认为 false）
        /// </summary>
        public bool EnableResponseContentLog { get; set; } = false;
    }

    /// <summary>
    /// 接口Get请求选项
    /// </summary>
    public class WebApiGetOption
    {
        /// <summary>
        /// 接口路径
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// 请求数据
        /// </summary>
        public object Data { get; set; }

        /// <summary>
        /// 加密请求数据
        /// </summary>
        public bool Encrypt { get; set; }

        /// <summary>
        /// 需要添加的请求标头集合
        /// </summary>
        public IDictionary<string, string> Headers { get; set; }

        /// <summary>
        /// 取消操作对象
        /// </summary>
        public CancellationTokenSource CancellationSource { get; set; }

        /// <summary>
        /// 请求日志配置
        /// </summary>
        public WebApiLogConfig Log { get; set; }
    }

    /// <summary>
    /// 接口Post请求选项
    /// </summary>
    public class WebApiPostOption : WebApiGetOption
    {
        /// <summary>
        /// 接口请求内容类型
        /// </summary>
        public WebApiContentType ContentType { get; set; } = WebApiContentType.Json;

        /// <summary>
        /// 上传的文件路径
        /// </summary>
        public string UploadFilePath { get; set; }
    }

    /// <summary>
    /// 接口请求下载选项
    /// </summary>
    public class WebApiDownloadOption : WebApiPostOption
    {
        /// <summary>
        /// 下载保存的文件路径
        /// </summary>
        public string SaveFilePath { get; set; }

        /// <summary>
        /// Http请求方法
        /// </summary>
        public HttpMethod Method { get; set; } = HttpMethod.Post;

        /// <summary>
        /// 下载进度回调函数
        /// </summary>
        public Action<WebApiDownloadProgressArgs> ProgressCallback { get; set; }
    }

    /// <summary>
    /// 接口请求内容类型
    /// </summary>
    public enum WebApiContentType
    {
        /// <summary>
        /// 无内容
        /// </summary>
        None,

        /// <summary>
        /// application/json格式内容
        /// </summary>
        Json,

        /// <summary>
        /// application/x-www-form-urlencoded格式内容
        /// </summary>
        FormUrlEncoded,

        /// <summary>
        /// 文件
        /// </summary>
        File
    }

    /// <summary>
    /// 接口下载进度参数
    /// </summary>
    public struct WebApiDownloadProgressArgs
    {
        /// <summary>
        /// 已传输字节数
        /// </summary>
        public long TransferredBytes { get; internal set; }

        /// <summary>
        /// 总字节数
        /// </summary>
        public long TotalBytes { get; internal set; }

        /// <summary>
        /// 下载进度
        /// </summary>
        public int ProgressPercentage { get; internal set; }
    }

    public class WebApiClientBoolResultException : Exception
    {
        public WebApiClientBoolResultException()
        {

        }

        public WebApiClientBoolResultException(BoolResult result) : base(result.Msg)
        {
            Result = result;
        }

        public WebApiClientBoolResultException(BoolResult result, HttpResponseMessage response) : base(result.Msg)
        {
            Response = response;
            Result = result;
        }

        public WebApiClientBoolResultException(string message) : base(message)
        {

        }

        public WebApiClientBoolResultException(string message, Exception innerException) : base(message, innerException)
        {

        }

        public HttpResponseMessage Response { get; }
        public BoolResult Result { get; }
    }

    public class WebApiClientDownloadException : Exception
    {
        public WebApiClientDownloadException()
        {

        }

        public WebApiClientDownloadException(BoolResult result) : base(result.Msg)
        {
            Result = result;
        }

        public WebApiClientDownloadException(BoolResult result, HttpResponseMessage response) : base(result.Msg)
        {
            Response = response;
            Result = result;
        }

        public WebApiClientDownloadException(string message) : base(message)
        {

        }

        public WebApiClientDownloadException(string message, Exception innerException) : base(message, innerException)
        {

        }

        public HttpResponseMessage Response { get; }
        public BoolResult Result { get; }
    }
}